#include "fiber.h"
#include "config.h"
#include"log.h"
#include "macro.h"
#include"scheduler.h"
#include <atomic>
#include <utility>

namespace yuechuan {
    static Logger::ptr g_logger = YUECHUAN_LOG_NAME("root");
    static std::atomic<uint64_t> s_fiber_id{0};         /// 全局原子量，用于协程创建时id的自增
    static std::atomic<int64_t> s_fiber_count{0};       /// 全局原子量，用于协程计数

    /*
        线程中会创建一个主协程，其他协程通过主协程来进行释放和创建，子协程执行完毕之后只能回到主协程，并让出空间
            Thread -> MainFiber  --> subFiber
                        |
                        |
                        V
                      subFiber

    */
    static thread_local Fiber *t_fiber = nullptr;                            /// 当前的协程
    static thread_local Fiber::ptr t_rootFiber = nullptr;                   /// Main协程的智能指针

    // 协程栈的大小，根据不同的需求其大小是不同的，可以在以后的使用中通过yaml文件来对其大小进行修改
    static ConfigVar<uint32_t>::ptr g_fiber_stack_size = Config::Lookup<uint32_t>("fiber.stack_size",
                                                                                  128 * 1024,
                                                                                  "fiber stack size");

    /**
     * @brief 内存分配器
     * @details 因为协程继承与enable_shared_from_this 这个类要求其子类对象的创建不能在栈上进行
     */
    class MallocStackAllocator {
    public:
        /**
         * @brief 创建空间
         * @param size 需要创建空间的大小
         * @return void*
         */
        static void *Alloc(size_t size) {
            return malloc(size);
        }

        /**
         * @brief 释放空间
         * @param vp 需要释放的指针
         * @param size 释放空间大小，针对于不同的释放方法，使用的参数是不同的，调用free方法，该参数是没有使用的
         */
        static void Dealloc(void *vp, size_t size)       // 这里传入第二个参数没有使用，针对不同的释放方法，使用的参数是不同的，
        {
            return free(vp);
        }
    };

    using StackALlocator = MallocStackAllocator;

    Fiber::Fiber() {
        m_state = EXEC;
        SetThis(this);

        if (getcontext(&m_ctx)) {
            YUECHUAN_ASSERT2(false, "getContext Error");
        }
        ++s_fiber_count;
        YUECHUAN_LOG_INFO(g_logger) << "Fiber::Fiber Main id [ " << m_id << " ] ";
    }

    Fiber::Fiber(std::function<void()> cb, size_t stackSize, bool user_caller)
            : m_id(++s_fiber_id), m_cb(std::move(cb)) {
        ++s_fiber_count;
        m_stackSize = stackSize ? stackSize : g_fiber_stack_size->getValue();

        m_stack = StackALlocator::Alloc(m_stackSize);
        if (getcontext(&m_ctx)) {
            YUECHUAN_ASSERT2(false, "getContext error");
        }
        m_ctx.uc_link = nullptr;                           /// 子协程的上下文关联的上下文，结束后的跳转设置为nullptr
        m_ctx.uc_stack.ss_sp = m_stack;                                  /// 设置协程的栈指针
        m_ctx.uc_stack.ss_size = m_stackSize;                            /// 设置协程的栈大小
        if (!user_caller) {
            makecontext(&m_ctx, &MainFunc, 0);                     /// 创建一个新的上下文，关联一个默认的执行函数
        } else {
            makecontext(&m_ctx, &CallerMainFunc, 0);
        }
        YUECHUAN_LOG_INFO(g_logger) << "Fiber::Fiber(arg...) Create id [ " << m_id << " ]";
    }

    Fiber::~Fiber() {
        --s_fiber_count;
        if (m_stack) {                                         // 针对于主协程是不会为其分配栈空间的，这个判断是针对子协程
            YUECHUAN_ASSERT(m_state == TERM || m_state == INIT || m_state == EXCE);
            StackALlocator::Dealloc(m_stack, m_stackSize);
        } else {                                                                    // 没有栈，其为主协程
            YUECHUAN_ASSERT(!m_cb);
            YUECHUAN_ASSERT(m_state == EXEC);

            Fiber *cur = t_fiber;
            if (cur == this) {
                SetThis(nullptr);
            }
        }
        YUECHUAN_LOG_INFO(g_logger) << "Fiber::~Fiber id [ " << m_id << " ]";
    }

    void Fiber::reset(std::function<void()> cb) {
        YUECHUAN_ASSERT(m_stack);                                 // 当协程结束后，其创建的空间未被释放，可以将其分配给其他协程
        YUECHUAN_ASSERT(m_state == TERM || m_state == INIT || m_state == EXCE);  // 重新分配空间的协程其状态要是结束或者初始化

        m_cb = std::move(cb);
        if (getcontext(&m_ctx))                                                    // 将传入的上下文初始化为当前活动状态的上下文
        {
            YUECHUAN_ASSERT2(false, "getContext error");
        }
        m_ctx.uc_link = nullptr;
        m_ctx.uc_stack.ss_sp = m_stack;
        m_ctx.uc_stack.ss_size = m_stackSize;                                    // 对上下文进行还原操作

        makecontext(&m_ctx, &MainFunc, 0);                                 /// 设置回调函数
        m_state = INIT;                                                         /// 设置协程状态为Init初始化状态
    }

    void Fiber::call() {
        SetThis(this);
        m_state = EXEC;
        if (swapcontext(&t_rootFiber->m_ctx, &m_ctx)) {
            YUECHUAN_ASSERT2(false, "swapContext Error");
        }
    }

    void Fiber::back() {
        SetThis(t_rootFiber.get());
        if (swapcontext(&m_ctx, &t_rootFiber->m_ctx)) {
            YUECHUAN_ASSERT2(false, "swapContext Error")
        }
    }

    void Fiber::swapIn() {
        SetThis(this);
        YUECHUAN_ASSERT(m_state != EXEC);                                    /// 要移动的协程状态不能是正在运行中的
        m_state = EXEC;                                                        /// 将其状态转换为运行中
        if (swapcontext(&Scheduler::GetMainFiber()->m_ctx, &m_ctx)) {       /// 将两个上下文进行切换
            YUECHUAN_ASSERT2(false, "swapContext Error");
        }
    }

    void Fiber::swapOut() {
        SetThis(Scheduler::GetMainFiber());
        if (swapcontext(&m_ctx, &Scheduler::GetMainFiber()->m_ctx)) {
            YUECHUAN_ASSERT2(false, "swapContext Error");
        }

    }

    void Fiber::SetThis(Fiber *fb) {
        t_fiber = fb;
    }

    Fiber::ptr Fiber::GetThis() {
        if (t_fiber) {
            return t_fiber->shared_from_this();           // 当第二次调用GetThis时，其是已经分配空间的，所以不会创建一个新的主协程
        }
        Fiber::ptr main_fiber(new Fiber);     // 静态成员变量还未创建，说明该线程的主协程还未创建，直接创建主协程即可，调用的是其无参构造函数
        YUECHUAN_ASSERT(t_fiber == main_fiber.get());
        t_rootFiber = main_fiber;
        return t_fiber->shared_from_this();
    }

    uint64_t Fiber::TotalFibers() {
        return s_fiber_count;
    }

    void Fiber::YieldToHold() {
        Fiber::ptr cur = GetThis();
        if (cur == t_rootFiber)
            return;

        cur->m_state = HOLD;
        cur->swapOut();
    }

    void Fiber::YieldToReady() {
        Fiber::ptr cur = GetThis();
        if (cur == t_rootFiber)
            return;

        cur->m_state = READY;
        cur->swapOut();
    }

    void Fiber::CallerMainFunc() {
        YUECHUAN_LOG_INFO(g_logger) << "CallerMainFunc";
        Fiber::ptr cur = GetThis();
        try {
            cur->m_cb();
            cur->m_cb = nullptr;
            cur->m_state = TERM;

        } catch (const std::exception &e) {
            cur->m_state = EXCE;
            YUECHUAN_LOG_ERROR(g_logger) << "Fiber Except: " << e.what()
                                         << "Fiber_id = " << cur->getId()
                                         << std::endl
                                         << yuechuan::BacktraceToString();

        } catch (...) {
            cur->m_state = EXCE;
            YUECHUAN_LOG_ERROR(g_logger) << "Fiber Except "
                                         << " Fiber_id = " << cur->getId()
                                         << std::endl
                                         << yuechuan::BacktraceToString();
        }
        auto raw_ptr = cur.get();
        cur.reset();
        raw_ptr->back();
        YUECHUAN_ASSERT2(false, "never reach fiber_id = " + std::to_string(raw_ptr->getId()));

    }

    void Fiber::MainFunc() {
        Fiber::ptr cur = GetThis();
        YUECHUAN_ASSERT(cur);
        try {
            cur->m_cb();                                      // 执行其绑定的回调函数
            cur->m_cb = nullptr;                              // 将回调函数置为空，防止回调函数中存在只能指针，引用计数会加一
            cur->m_state = TERM;                              // 执行完毕之后将协程设置为结束状态

        } catch (std::exception &ex) {
            cur->m_state = EXCE;
            YUECHUAN_LOG_ERROR(g_logger) << "Fiber except" << ex.what()
                                         << " fiber_id=" << cur->getId()
                                         << std::endl
                                         << yuechuan::BacktraceToString();
        } catch (...) {
            cur->m_state = EXCE;
            YUECHUAN_LOG_ERROR(g_logger) << "Fiber Except" << " fiber_id =" << cur->getId()
                                         << std::endl << yuechuan::BacktraceToString();
        }
        /// 因为创建子协程时将uc_link指定为t_rootFiber所以当该函数执行完毕之后会直接切换到主协程
        /// 这里还是要使用到野指针，这里一定需要swapOut因为在swapOut中对主协程进行了处理，如果直接利用uc_link会导致主协程回到主协程
        auto raw_ptr= cur.get();
        cur.reset();
        raw_ptr->swapOut();
    }

    uint64_t Fiber::GetFiberId() {
        if (t_fiber) {
            return t_fiber->getId();
        }
        return 0;
    }
} // namespace yuechuan
